/*
	Copyright 2013 Francesco "Franc[e]sco" Noferi (francesco1149@gmail.com)

	This file is part of silent-ag-reloaded.

	silent-ag is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	silent-ag is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with silent-ag.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "silent-ag.hpp"
#include <iostream>
#include <tchar.h>
#include <shellapi.h>
#include "resource.h"
#include "utils.h"

// custom wnd messages
#define WM_TRAY (WM_USER + 1)

namespace silentag
{
	LPCTSTR app::appname = _T("Silent Aero Glass Reloaded alpha-r0");
	LPCTSTR app::classname = _T("silentagwnd");
	LPCTSTR app::msgcaption = _T("Aero Glass for DWM"); // TODO: put this in a config file

	// static singleton instance
	std::auto_ptr<app> app::instance;

	// returns the singleton
	app *app::get()
	{
		if (!instance.get())
			instance = std::auto_ptr<app>(new app());

		return instance.get();
	}

	// protected ctor, prevents illegal instantiation of the singleton
	app::app()
		: hInstance(NULL),
		  hThread(NULL),
		  hWnd(NULL), 
		  hPopupMenu(NULL)
	{
		// TODO: wrap tray icon in a class
		SecureZeroMemory(&nid, sizeof(NOTIFYICONDATA));
	}

	app::~app()
	{
		if (hThread)
			stopsuppress();
	}

	int app::run(HINSTANCE hInstance)
	{
		WNDCLASSEX wc;
		MSG msg;

		this->hInstance = hInstance;

		// enable console if debugging
	#if _DEBUG
		utils::enablewin32console();
	#endif

		// create invisible window
		wc.cbSize = sizeof(WNDCLASSEX);
		wc.style = 0;
		wc.lpfnWndProc = wndproc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hInstance = hInstance;
		wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON2));
		wc.hCursor = LoadCursor(NULL, IDC_ARROW);
		wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
		wc.lpszMenuName  = NULL;
		wc.lpszClassName = classname;
		wc.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON2));

		#if _DEBUG
			std::tcout << _T("attempting to register window class... ");
		#endif
		if(!RegisterClassEx(&wc))
		{
		#if _DEBUG
			std::tcout << _T("failed! lasterror = ") << GetLastError() << tendl;
		#endif
			MessageBox(NULL, _T("Failed to register window class!"), appname, MB_ICONERROR | MB_OK);

			return EXIT_FAILURE;
		}

	#if _DEBUG
		std::tcout << _T("done!") << tendl;
		std::tcout << _T("attempting to create invisible window") << tendl;
	#endif

		hWnd = CreateWindowEx(
			WS_EX_CLIENTEDGE,
			classname,
			appname,
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT, CW_USEDEFAULT, 1, 1,
			NULL, NULL, hInstance, NULL
		);

		if (!hWnd)
		{
		#if _DEBUG
			std::tcout << _T("failed to create invisible window! lasterror = ") << GetLastError() << tendl;
		#endif
			MessageBox(NULL, _T("Failed to create invisible window!"), appname, MB_ICONERROR | MB_OK);

			return EXIT_FAILURE;
		}

	#if _DEBUG
		std::tcout << _T("created invisible window!") << tendl;
	#endif

		ShowWindow(hWnd, SW_HIDE); // invisible window
		UpdateWindow(hWnd);

	#if _DEBUG
		std::tcout << _T("the message loop will now start") << tendl;
	#endif

		// message loop
		while(GetMessage(&msg, NULL, 0, 0) > 0)
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

	#if _DEBUG
		std::tcout << _T("message loop ended with a return code of ") << msg.wParam << tendl;
	#endif

		traydestroy();

		return msg.wParam;
	}

	bool app::trayinit()
	{
		// initialize tray icon
		nid.cbSize = sizeof(NOTIFYICONDATA);
		nid.hWnd = hWnd;
		nid.uID = 100;
		nid.uVersion = NOTIFYICON_VERSION;
		nid.uCallbackMessage = WM_TRAY;
		nid.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON2));
		_tcscpy_s(nid.szTip, 128, appname);
		nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;

		Shell_NotifyIcon(NIM_ADD, &nid);

		// initialize context menu
		hPopupMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));

		return true;
	}

	void app::traydestroy()
	{
		// the tray icon must be manually deleted
		nid.uFlags = 0;
		Shell_NotifyIcon(NIM_DELETE, &nid);
	}

	void app::traychangeicon(LPCTSTR icon)
	{
		nid.hIcon = LoadIcon(hInstance, icon);
		nid.uFlags = NIF_ICON;
		Shell_NotifyIcon(NIM_MODIFY, &nid);
	}

	void app::traymessage(LPCTSTR text, LPCTSTR icon)
	{
		_tcscpy_s(nid.szInfoTitle, 128, appname);
		_tcscpy_s(nid.szInfo, 128, text);
		nid.hBalloonIcon = LoadIcon(hInstance, icon);
		nid.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON;
		nid.uFlags = NIF_INFO;
		Shell_NotifyIcon(NIM_MODIFY, &nid);
	}

	void app::showcontextmenu()
	{
		POINT cursor = {0};

		GetCursorPos(&cursor);
		TrackPopupMenu(GetSubMenu(hPopupMenu, 0), TPM_LEFTALIGN, cursor.x, cursor.y, 0, hWnd, NULL);
	}

	void app::startsuppress()
	{
		if (hThread)
		{
		#if _DEBUG
			std::tcout << _T("the message suppress thread is already running!") << tendl;
		#endif

			traymessage(_T("Could not enable silent-ag: the message suppression is already running!"), IDI_WARNING);
			return;
		}

	#if _DEBUG
		std::tcout << _T("starting hidemessage thread... ");
	#endif
		hThread = CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(hidemessage), NULL, 0, NULL);

		if (!hThread)
		{
		#if _DEBUG
			std::tcout << _T("failed! lasterror = ") << GetLastError() << tendl;
		#endif

			traymessage(_T("Failed to start message suppression thread!"), IDI_WARNING);

			return;
		}

	#if _DEBUG
		std::tcout << _T("done!") << tendl;
	#endif

		traychangeicon(MAKEINTRESOURCE(IDI_ICON1));
		traymessage(_T("Now suppressing the Aero Glass demo messagebox"), IDI_INFORMATION);
	}

	void app::stopsuppress()
	{
		HWND hMessageBox = NULL;

		if (!hThread)
		{
		#if _DEBUG
			std::tcout << _T("the message suppress thread not running!") << tendl;
		#endif

			traymessage(_T("Could not disable suppression: no message suppression is running!"), IDI_WARNING);
			return;
		}

	#if _DEBUG
		std::tcout << _T("terminating hidemessage thread... ");
	#endif
		if (!TerminateThread(hThread, 0))
		{
		#if _DEBUG
			std::tcout << _T("failed! lasterror = ") << GetLastError() << tendl;
		#endif

			traymessage(_T("Could not disable suppression: failed to kill thread!"), IDI_WARNING);
			return;
		}

		hThread = NULL;

	#if _DEBUG
		std::tcout << _T("done!") << tendl;
	#endif

		// make the messagebox visible again
		hMessageBox = FindWindow(NULL, msgcaption);
		
		if (IsWindow(hMessageBox) && !IsWindowVisible(hMessageBox))
			ShowWindow(hMessageBox, SW_SHOW);

		traychangeicon(MAKEINTRESOURCE(IDI_ICON2));
		traymessage(_T("Stopped suppressing the Aero Glass demo messagebox"), IDI_INFORMATION);
	}

	void app::togglesuppress()
	{
		HMENU hMenu = GetSubMenu(hPopupMenu, 0);

		// toggle enabled menu item and hide/show window
		if (GetMenuState(hMenu, 1, MF_BYPOSITION) & MF_CHECKED)
		{
			CheckMenuItem(hMenu, 1, MF_BYPOSITION | MF_UNCHECKED);
			stopsuppress();
		}
		else
		{
			CheckMenuItem(hMenu, 1, MF_BYPOSITION | MF_CHECKED);
			startsuppress();
		}
	}

	// thread that finds the messagebox and hides it
	DWORD app::hidemessage(LPVOID lpvParams)
	{
		HWND hMessageBox = NULL;

		while (true)
		{
		#if _DEBUG
			std::tcout << _T("looking for the messagebox window...");
		#endif

			while (!hMessageBox)
			{
				hMessageBox = FindWindow(NULL, msgcaption); 
				Sleep(50);
			}

		#if _DEBUG
			std::tcout << _T(" found!") << tendl;
			std::tcout << _T("now keeping window hidden") << tendl;
		#endif

			while (IsWindow(hMessageBox))
			{
				if (IsWindowVisible(hMessageBox))
					ShowWindow(hMessageBox, SW_HIDE);

				Sleep(50);
			}

		#if _DEBUG
			std::tcout << _T("window closed, now waiting for a new messagebox") << tendl;
		#endif

			hMessageBox = NULL;
		}

		return 00000000L;
	}

	// main window proc
	LRESULT CALLBACK app::wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		app *theapp = app::get();

		switch(uMsg)
		{
		case WM_CREATE:
			theapp->hWnd = hWnd;

			if (!theapp->trayinit())
				PostQuitMessage(0);

			theapp->togglesuppress();
			break;

		case WM_TRAY:
			switch(lParam)
			{
			case WM_RBUTTONUP:
				theapp->showcontextmenu();
				break;

			default:
				return DefWindowProc(hWnd, uMsg, wParam, lParam);
			};
			break;

		// handle context menu items
		case WM_COMMAND:
		{
			WORD wCommand = LOWORD(wParam);

			switch (wCommand)
			{
			case ID_CONTEXT_CLOSE:
				PostQuitMessage(0);
				break;

			case ID_CONTEXT_ENABLE:
				theapp->togglesuppress();
				break;

			case ID_CONTEXT_ABOUT:
				MessageBox(NULL, _T("A little tool that suppresses the demo messagebox in Aero Glass for Win8 RC4\n\n")
					_T("Copyright (C) 2013 by Francesco \"Franc[e]sco\" Noferi"), appname, MB_OK | MB_ICONINFORMATION);
				break;
			}
			break;
		}

		case WM_CLOSE:
			DestroyWindow(hWnd);
			break;

		case WM_DESTROY:
			PostQuitMessage(0);
			break;

		default:
			return DefWindowProc(hWnd, uMsg, wParam, lParam);
		}

		return 0;
	}
}